//  FastHypervolume.java
//
//  Authors:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2013 Antonio J. Nebro, Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

package etmo.qualityIndicator.fastHypervolume;

import etmo.core.Solution;
import etmo.core.SolutionSet;
import etmo.qualityIndicator.fastHypervolume.wfg.Front;
import etmo.qualityIndicator.fastHypervolume.wfg.WFGHV;
import etmo.util.comparators.ObjectiveComparator;

/**
 * Created with IntelliJ IDEA. User: Antonio J. Nebro Date: 26/08/13 Time: 10:20
 */
public class FastHypervolume {
	Solution referencePoint_;
	int numberOfObjectives_;
	double offset_ = 20.0;

	public FastHypervolume() {
		referencePoint_ = null;
		numberOfObjectives_ = 0;
	}

	public FastHypervolume(double offset) {
		referencePoint_ = null;
		numberOfObjectives_ = 0;
		offset_ = offset;
	}

	public double computeHypervolume(SolutionSet solutionSet) {
		double hv;
		if (solutionSet.size() == 0)
			hv = 0.0;
		else {
			numberOfObjectives_ = solutionSet.get(0).getNumberOfObjectives();
			referencePoint_ = new Solution(numberOfObjectives_);
			updateReferencePoint(solutionSet);
			if (numberOfObjectives_ == 2) {
				solutionSet.sort(new ObjectiveComparator(numberOfObjectives_ - 1, true));
				hv = get2DHV(solutionSet);
			} else {
				updateReferencePoint(solutionSet);
				Front front = new Front(solutionSet.size(), numberOfObjectives_, solutionSet);
				hv = new WFGHV(numberOfObjectives_, solutionSet.size(), referencePoint_).getHV(front);
			}
		}

		return hv;
	}

	public double computeHypervolume(SolutionSet solutionSet, Solution referencePoint) {
		double hv = 0.0;
		if (solutionSet.size() == 0)
			hv = 0.0;
		else {
			numberOfObjectives_ = solutionSet.get(0).getNumberOfObjectives();
			referencePoint_ = referencePoint;

			if (numberOfObjectives_ == 2) {
				solutionSet.sort(new ObjectiveComparator(numberOfObjectives_ - 1, true));

				hv = get2DHV(solutionSet);
			} else {
				WFGHV wfg = new WFGHV(numberOfObjectives_, solutionSet.size());
				Front front = new Front(solutionSet.size(), numberOfObjectives_, solutionSet);
				hv = wfg.getHV(front, referencePoint);
			}
		}

		return hv;
	}

	/**
	 * Updates the reference point
	 */
	private void updateReferencePoint(SolutionSet solutionSet) {
		double[] maxObjectives = new double[numberOfObjectives_];
		for (int i = 0; i < numberOfObjectives_; i++)
			maxObjectives[i] = 0;

		for (int i = 0; i < solutionSet.size(); i++)
			for (int j = 0; j < numberOfObjectives_; j++)
				if (maxObjectives[j] < solutionSet.get(i).getObjective(j))
					maxObjectives[j] = solutionSet.get(i).getObjective(j);

		for (int i = 0; i < referencePoint_.getNumberOfObjectives(); i++) {
			referencePoint_.setObjective(i, maxObjectives[i] + offset_);
		}
	}

	/**
	 * Computes the HV of a solution set. REQUIRES: The problem is bi-objective
	 * REQUIRES: The archive is ordered in descending order by the second
	 * objective
	 * 
	 * @return
	 */
	public double get2DHV(SolutionSet solutionSet) {
		double hv = 0.0;
		if (solutionSet.size() > 0) {
			hv = Math.abs((solutionSet.get(0).getObjective(0) - referencePoint_.getObjective(0))
					* (solutionSet.get(0).getObjective(1) - referencePoint_.getObjective(1)));

			for (int i = 1; i < solutionSet.size(); i++) {
				double tmp = Math.abs((solutionSet.get(i).getObjective(0) - referencePoint_.getObjective(0))
						* (solutionSet.get(i).getObjective(1) - solutionSet.get(i - 1).getObjective(1)));
				hv += tmp;
			}
		}
		return hv;
	}
	
	
	public void computeHVContributions(SolutionSet solutionSet, int taskId) {
		int start = solutionSet.get(0).getProblemSet().get(taskId)
				.getStartObjPos();
		int end = solutionSet.get(0).getProblemSet().get(taskId).getEndObjPos();

		SolutionSet pop = new SolutionSet(solutionSet.size());

		for (int i = 0; i < solutionSet.size(); i++) {
			Solution sol = new Solution(end - start + 1);
			for (int j = start; j <= end; j++)
				sol.setObjective(j - start, solutionSet.get(i).getObjective(j));
			pop.add(sol);
		}

		computeHVContributions(pop);

		for (int i = 0; i < solutionSet.size(); i++)
			solutionSet.get(i).setCrowdingDistance(
					pop.get(i).getCrowdingDistance());
	}

	/**
	 * Computes the HV contribution of the solutions
	 * 
	 * @return
	 */
	public void computeHVContributions(SolutionSet solutionSet) {
		double[] contributions = new double[solutionSet.size()];
		double solutionSetHV = 0;

		solutionSetHV = computeHypervolume(solutionSet);

		for (int i = 0; i < solutionSet.size(); i++) {
			Solution currentPoint = solutionSet.get(i);
			solutionSet.remove(i);

			if (numberOfObjectives_ == 2) {
				// updateReferencePoint(solutionSet);
				// solutionSet.sort(new
				// ObjectiveComparator(numberOfObjectives_-1, true));
				contributions[i] = solutionSetHV - get2DHV(solutionSet);
			} else {
				Front front = new Front(solutionSet.size(), numberOfObjectives_, solutionSet);
				double hv = new WFGHV(numberOfObjectives_, solutionSet.size(), referencePoint_).getHV(front);
				contributions[i] = solutionSetHV - hv;
			}
			solutionSet.add(i, currentPoint);
		}

		for (int i = 0; i < solutionSet.size(); i++) {
			solutionSet.get(i).setCrowdingDistance(contributions[i]);
		}
	}
}
